/*
 * JDOQL @
 * http://code.google.com/intl/sv-SE/appengine/docs/java/datastore/queriesandindexes.html
 */
package org.easypr.organizer.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import net.sf.jsr107cache.Cache;
import net.sf.jsr107cache.CacheException;
import net.sf.jsr107cache.CacheManager;

import org.easypr.organizer.client.BackboneService;
import org.easypr.organizer.shared.AppConstants;
import org.easypr.organizer.shared.MyContact;
import org.easypr.organizer.shared.MyMeeting;
import org.easypr.organizer.shared.MyUser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.google.gdata.client.GoogleService;
import com.google.gdata.client.Service.GDataRequest;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.gdata.util.ServiceException;
import com.google.gdata.client.authn.oauth.GoogleOAuthHelper;
import com.google.gdata.client.authn.oauth.GoogleOAuthParameters;
import com.google.gdata.client.authn.oauth.OAuthException;
import com.google.gdata.client.authn.oauth.OAuthHmacSha1Signer;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

// TODO: Auto-generated Javadoc
/**
 * The Class BackboneServiceImpl.
 */
@SuppressWarnings("serial")
public class BackboneServiceImpl extends RemoteServiceServlet implements BackboneService {

	/** The pm. */
	PersistenceManager pm;
	/** The request. */
	HttpServletRequest request;
	
	/** The session. */
	HttpSession session;
	
	/**
	 * Instantiates a new backbone service impl.
	 */
	public BackboneServiceImpl() {
		pm = PMF.get().getPersistenceManager();
	}
	
	/**
	 * Fetch user.
	 *
	 * @param user the user
	 * @return the my user
	 */
	@SuppressWarnings("unchecked")
	public MyUser fetchUser(MyUser user) {
		
		//String query = "select key from " + JDOUser.class.getName()+ " where email == '"+user.getEmail()+"'";
		Query query = pm.newQuery(JDOUser.class);
		query.setFilter("email == '"+user.getEmail()+"'");
		
		List<JDOUser> jdos = (List<JDOUser>) query.execute();
		if(jdos.isEmpty()) {
			return null;
		} else {
			user.setIsRegistered();
		}
		return user;
	}
	
	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#saveUser(org.easypr.organizer.shared.MyUser)
	 */
	public MyUser saveUser(MyUser user) {
		Key k = null;
		if(user.isRegistered()) {
			k = KeyFactory.createKey(MyUser.class.getSimpleName(), user.getEmail());
		} 
		JDOUser jdo = new JDOUser(k, user.getName(), user.getEmail(), user.getNickname());

		if(pm.isClosed()) {
			
		}
		
		try {
            pm.makePersistent(jdo);
        } finally {
            //pm.close();
        }
        user.setIsRegistered();
    	return user;
	}
	
	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#saveContact(org.easypr.organizer.shared.MyContact)
	 */
	public MyContact saveContact(MyContact contact) {
		Key k = null;
		if(contact.getId() > 0) {
			k = KeyFactory.createKey(JDOContact.class.getSimpleName(), contact.getId());
		}
		
		JDOContact jdo = new JDOContact(k, contact.getOwner(), contact.getName(), contact.getEmail(), contact.getCellphone(),
				contact.getWorkphone(), contact.getWorkplace(), contact.getBackground());
		
		
		try {
			pm.makePersistent(jdo);
		} finally {
			//pm.close();
		}
		return contact;
	}
	
	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#fetchContacts(org.easypr.organizer.shared.MyUser, java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public ArrayList<MyContact> fetchContacts(MyUser user, String filter) {
		//String query = "select from " + JDOContact.class.getName()+ " where belongTo == '"+user.getEmail()+"' order by name asc";
		ArrayList<MyContact> ret;
		/*
		Cache cache;
		try {
			cache = CacheManager.getInstance().getCacheFactory().createCache(Collections.emptyMap());
		} catch (CacheException e) {
			return null;
		}
		
		if(filter == null) {
			ret = (ArrayList<MyContact>) cache.get("contacts_" + user.getEmail());
			if(ret != null && ret.size() > 0) {
				return ret;
			}
		}
		*/
		
		Query query = pm.newQuery(JDOContact.class);
		query.setOrdering("name asc");
		if(filter != null) {
			query.setFilter("belongTo == '"+user.getEmail()+"' && firstLetter == '"+filter.toLowerCase()+"'");
		} else {
			query.setFilter("belongTo == '"+user.getEmail()+"'");
		}
		
		List<JDOContact> jdos = (List<JDOContact>) query.execute();
		if(jdos.isEmpty()) {
			return null;
		} else {
			ret = new ArrayList<MyContact>();
			MyContact contact = null;
			for(int i=0; i<jdos.size(); i++) {
				if(filter != null) {
					if(jdos.get(i).getName().substring(0, 1).equals(filter)) {
						contact = new MyContact(user.getEmail(), jdos.get(i).getName(), jdos.get(i).getEmail(), jdos.get(i).getCellphone(),
								jdos.get(i).getWorkphone(), jdos.get(i).getWorkplace(), jdos.get(i).getBackground());
						
						long id = jdos.get(i).getKey().getId();
						contact.setId(id);
						
						ret.add(contact);
					}
				} else {
					contact = new MyContact(user.getEmail(), jdos.get(i).getName(), jdos.get(i).getEmail(), jdos.get(i).getCellphone(),
							jdos.get(i).getWorkphone(), jdos.get(i).getWorkplace(), jdos.get(i).getBackground());
					
					long id = jdos.get(i).getKey().getId();
					contact.setId(id);
					ret.add(contact);
				}
			}
			//cache.put("contacts_"+user.getEmail(), ret);
			return ret;
		}
	}
	
	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#getById(long)
	 */
	public MyContact getById(long id) {
		JDOContact contact = null;
		MyContact ret = null;
		Key k = KeyFactory.createKey(JDOContact.class.getSimpleName(), id);
		contact = pm.getObjectById(JDOContact.class, k);
		
		if(contact == null) {
			System.out.println("Ingen användare hittade med id " + id);
		} else {
			ret = new MyContact("", contact.getName(), contact.getEmail(), contact.getCellphone(),
					contact.getWorkphone(), contact.getWorkplace(), contact.getBackground());
			ret.setId(id);
		}
		
		return ret;
	}

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#search(java.lang.String, org.easypr.organizer.shared.MyUser)
	 */
	@SuppressWarnings("unchecked")
	/**
	 * TODO: Full text search http://googleappengine.blogspot.com/2010/04/making-your-app-searchable-using-self.html
	 */
	public ArrayList<MyContact> search(String searchString, MyUser user) {
		
		//String query = "select from " + JDOContact.class.getName()+ " where belongTo == '"+user.getEmail()+"' order by name asc";
		Query query = pm.newQuery(JDOContact.class);
		query.setFilter("belongTo == '"+user.getEmail()+"'");
		query.setOrdering("name asc");
		List<JDOContact> jdos = (List<JDOContact>) query.execute();
		if(jdos.isEmpty()) {
			return null;
		} else {
			ArrayList<MyContact> ret = new ArrayList<MyContact>();
			MyContact contact = null;
			for(int i=0; i<jdos.size(); i++) {
				if(
					jdos.get(i).getName().toLowerCase().contains(searchString.toLowerCase()) ||
					jdos.get(i).getWorkplace().toLowerCase().contains(searchString.toLowerCase()) ||
					jdos.get(i).getEmail().toLowerCase().contains(searchString.toLowerCase())
				) {
					contact = new MyContact(user.getEmail(), jdos.get(i).getName(), jdos.get(i).getEmail(), jdos.get(i).getCellphone(),
							jdos.get(i).getWorkphone(), jdos.get(i).getWorkplace(), jdos.get(i).getBackground());
					ret.add(contact);
				}
			}
			return ret;
		}
	}

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#liveSearch(java.lang.String, org.easypr.organizer.shared.MyUser)
	 */
	@SuppressWarnings("unchecked")
	public ArrayList<MyContact> liveSearch(String searchString, MyUser user) {
		//String query = "select from " + JDOContact.class.getName()+ " where belongTo == '"+user.getEmail()+"' order by name asc";
		Query query = pm.newQuery(JDOContact.class);
		query.setFilter("belongTo == '"+user.getEmail()+"'");
		query.setOrdering("name asc");
		List<JDOContact> jdos = (List<JDOContact>) query.execute();
		ArrayList<MyContact> ret = new ArrayList<MyContact>();
		if(jdos.isEmpty()) {
			return ret;
		} else {
			MyContact contact = null;
			CharSequence searchFor = (CharSequence)searchString.toLowerCase();
			int max = (jdos.size() >= AppConstants.SEARCH_DROPDOWN_LIMIT ? AppConstants.SEARCH_DROPDOWN_LIMIT : jdos.size());
			for(int i=0; i<max; i++) {
				if(
					jdos.get(i).getName().toLowerCase().contains(searchFor) ||
					jdos.get(i).getWorkplace().toLowerCase().contains(searchFor)
				) {
					contact = new MyContact(user.getEmail(), jdos.get(i).getName(), jdos.get(i).getEmail(), jdos.get(i).getCellphone(),
							jdos.get(i).getWorkphone(), jdos.get(i).getWorkplace(), jdos.get(i).getBackground());
					contact.setId(jdos.get(i).getKey().getId());
					ret.add(contact);
				}
			}
			return ret;
		}
	}

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#saveMeeting(org.easypr.organizer.shared.MyMeeting)
	 */
	@Override
	public MyMeeting saveMeeting(MyMeeting meeting) {

		Key k = null;
		if(meeting.getId() > 0) {
			k = KeyFactory.createKey(MyContact.class.getSimpleName(), meeting.getId());
		}
		
		JDOMeeting jdo = new JDOMeeting(k, meeting.getContactId(), meeting.getOwner(), meeting.getDate(), meeting.getNotes());
		
		try {
			pm.makePersistent(jdo);
		} finally {
			//pm.close();
		}
		return meeting;
	}

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#getMeetings(org.easypr.organizer.shared.MyUser, long, int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public ArrayList<MyMeeting> getMeetings(MyUser user, long contactid, int beforeTime) {
		Query query = pm.newQuery(JDOMeeting.class);
		if(beforeTime > 0) {
			query.setFilter("date < " + Integer.toString(beforeTime) + " && belongTo == '" + user.getEmail() + "' && contactid == " + Long.toString(contactid));
		} else {
			query.setFilter("belongTo == '" + user.getEmail() + "' && contactid == " + Long.toString(contactid));
		}
		query.setOrdering("date desc");
		query.setRange(0, AppConstants.DISPLAY_MEETINGS_LIMIT);
		
		List<JDOMeeting> jdos = (List<JDOMeeting>) query.execute();
		ArrayList<MyMeeting> ret = new ArrayList<MyMeeting>();
		if(jdos.isEmpty()) {
			return ret;
		} else {
			for(int i=0; i<jdos.size(); i++) {
				MyMeeting meeting = new MyMeeting(jdos.get(i).getBelongTo(), contactid, jdos.get(i).getDate(), jdos.get(i).getNotes());
				meeting.setId(jdos.get(i).getKey().getId());
				ret.add(meeting);
			}
			return ret;
		}
	}

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#getMeetingsNavigation(java.lang.String, long, int)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public ArrayList<Integer> getMeetingsNavigation(String userEmail, long contactid, int beforeTime) {
		ArrayList ret = new ArrayList<Integer>();
		ret.add(0);
		ret.add(0);
		Query query = null;
		if(beforeTime > 0) {
			// Check if we have any previous meeting notes
			query = pm.newQuery(JDOMeeting.class);
			query.setFilter("date >= " + Integer.toString(beforeTime) + " && belongTo == '" + userEmail + "' && contactid == " + Long.toString(contactid));
			query.setOrdering("date asc");
			query.setRange(0, AppConstants.DISPLAY_MEETINGS_LIMIT+1);
			List<JDOMeeting> newer_jdos = (List<JDOMeeting>) query.execute();
			if(newer_jdos.size() == AppConstants.DISPLAY_MEETINGS_LIMIT+1) {
				ret.set(0, newer_jdos.get(AppConstants.DISPLAY_MEETINGS_LIMIT).getDate());
			} else {
				// Special
				ret.set(0, 1);
			}
		}
		//System.out.println("Letar efter: " +beforeTime);
		// Check if we need a Next link
		query = pm.newQuery(JDOMeeting.class);
		if(beforeTime == 0) {
			query.setFilter("date > " + Integer.toString(beforeTime) + " && belongTo == '" + userEmail + "' && contactid == " + Long.toString(contactid));
		} else {
			
			query.setFilter("date < " + Integer.toString(beforeTime) + " && belongTo == '" + userEmail + "' && contactid == " + Long.toString(contactid));
		}
		query.setOrdering("date desc");
		query.setRange(0, AppConstants.DISPLAY_MEETINGS_LIMIT+1);
		List<JDOMeeting> older_jdos = (List<JDOMeeting>) query.execute();
		if(older_jdos.size() == AppConstants.DISPLAY_MEETINGS_LIMIT+1) {
			ret.set(1, 1);
		}
		
		return ret;
	}
	

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#getOAuthRequestURL(org.easypr.organizer.shared.MyUser, java.lang.String)
	 */
	@Override
	public String getOAuthRequestURL(MyUser user, String callbackURL) {
		
		GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
		oauthParameters.setOAuthConsumerKey(AppConstants.OAuthConsumerKey);
	    oauthParameters.setOAuthConsumerSecret(AppConstants.OAuthConsumerSecret);
	    oauthParameters.setScope("http://www.google.com/m8/feeds/");
	    oauthParameters.setOAuthCallback(callbackURL);
	    
	    String requestUrl = "";
	    try {
	    	// Retrieve the unauthorized request token and token secret.
	    	GoogleOAuthHelper oauthHelper = new GoogleOAuthHelper(new OAuthHmacSha1Signer());
	    	oauthHelper.getUnauthorizedRequestToken(oauthParameters);
	    	
	    	requestUrl = oauthHelper.createUserAuthorizationUrl(oauthParameters);

	    	if(request == null) {
				request = this.getThreadLocalRequest();
				session = request.getSession();
			}
	    	if(session.getAttribute("tokensecret") == null) {
	    		user.setOAuthTokenSecret(oauthParameters.getOAuthTokenSecret());
	    		session.removeAttribute("User");
	    		session.setAttribute("User", user);
			}
	    	
	    } catch (OAuthException oex) {
	    	System.out.println("Exception!");
	    }
	    // Set the user authorization url as a request attribute, since we want to
	    // display it to the user before redirecting.  If this weren't an example,
	    // you could just redirect the user to the authorization url at this point.
	    //request.setAttribute(USER_AUTHORIZATION_URL_KEY, requestUrl);
	    return requestUrl;
	}


	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#fetchGoogleContacts(org.easypr.organizer.shared.MyUser, java.lang.String)
	 */
	@Override
	// http://code.google.com/intl/sv-SE/apis/gdata/docs/auth/oauth.html
	public ArrayList<MyContact> fetchGoogleContacts(MyUser user, String nextFeedURL) {
		if(request == null) {
			request = this.getThreadLocalRequest();
			session = request.getSession();
		}
    	
		
		ArrayList<MyContact> ret = new ArrayList<MyContact>();
		
		String accessToken = user.getAccessToken();
		String accessTokenSecret = user.getOAuthTokenSecret();
		
		GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
    	oauthParameters.setOAuthConsumerKey(AppConstants.OAuthConsumerKey);
	    oauthParameters.setOAuthConsumerSecret(AppConstants.OAuthConsumerSecret);
	    oauthParameters.setOAuthToken(accessToken);
	    oauthParameters.setOAuthTokenSecret(accessTokenSecret);
    	
    	URL feedUrl;
        // Convert the feed url string to a {@link URL} object.
    	String feedUrlString;
    	if(nextFeedURL == null) {
	        feedUrlString = AppConstants.GoogleContactsFeedURL;
	        feedUrlString += feedUrlString.indexOf("?") > 0 ? "&" : "?";
	        feedUrlString += "prettyprint=true";
    	} else {
    		feedUrlString = nextFeedURL;
    	}
        try {
			feedUrl = new URL(feedUrlString);
		} catch (MalformedURLException e1) {
			return null;
		}
	    
        GoogleService googleService = new GoogleService(AppConstants.GoogleContactsServiceName, "auddenberg-easyprorganizer-v1");
        
	    try {
	    	googleService.setOAuthCredentials(oauthParameters, new OAuthHmacSha1Signer());
	    	
	    	GDataRequest gdRequest = googleService.createFeedRequest(feedUrl);
	    	
	    	gdRequest.execute();
    	
	        // Stream the xml feed to the output.
	        InputStream is  = gdRequest.getResponseStream();
	        BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
	        StringBuilder sb = new StringBuilder();
	        String line = null;
	        try {
	            while ((line = reader.readLine()) != null) {
	                sb.append(line + "\n");
	            }
	        } catch (IOException e) {
	        } finally {
	          try {
	            is.close();
	          } catch (IOException e) {
	          }
	        }
	        //request.setAttribute("feed_raw_xml", sb.toString());
	        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
	        Document doc;
			try {

				//Using factory get an instance of document builder
				DocumentBuilder db = dbf.newDocumentBuilder();

				//parse using builder to get DOM representation of the XML file
				doc = db.parse(new InputSource(new StringReader(sb.toString())));


			}catch(ParserConfigurationException pce) {
				pce.printStackTrace();
				return null;
			}catch(SAXException se) {
				se.printStackTrace();
				return null;
			}catch(IOException ioe) {
				ioe.printStackTrace();
				return null;
			}
	        Element feedElement = doc.getDocumentElement();
	        
	        NodeList links = feedElement.getElementsByTagName("link");
	        for (int i = 0; i < links.getLength(); i++) {
	            Element link = (Element) links.item(i);
	            if(link.getAttribute("rel").equals("next")) {
	            	session.setAttribute("next", link.getAttribute("href").toString());
	            }
	        }
	        
	        NodeList entries = feedElement.getElementsByTagName("entry");
	        for(int i = 0; i<entries.getLength(); i++) {
	        	String address = "";
	        	String title = "";
	        	String orgName = "";
	        	String phone = "";
	        	
	        	Element entry = (Element) entries.item(i);
	        	title = getElementTextValue(entry, "title"); 
	        	NodeList emails = entry.getElementsByTagName("gd:email");
	        	if(emails != null && emails.getLength() == 1) {
	        		Element email = (Element) emails.item(0);
	        		address = email.getAttribute("address");
	        	}
	        	NodeList orgs = entry.getElementsByTagName("gd:organization");
	        	if(orgs != null && orgs.getLength() == 1) {
	        		Element organization = (Element) orgs.item(0);
	        		if(organization != null) {
	        			orgName = getElementTextValue(organization, "gd:orgName");
	        		} 
	        	}
	        	phone = getElementTextValue(entry, "gd:phoneNumber");
	        	
	        	MyContact contact = new MyContact(user.getEmail(), title, address, phone, phone, orgName, "");
	        	ret.add(contact);
	        	
	        }
	    } catch (IOException ioe) {
	    } catch (ServiceException se) {
	    } catch (OAuthException oe) {
	    } 
	    
		return ret;
	}
	
	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#getImportNextLink()
	 */
	public String getImportNextLink() {
		if(request == null) {
			request = this.getThreadLocalRequest();
			session = request.getSession();
		}
		
		if(session.getAttribute("next") != null) {
			return session.getAttribute("next").toString();
		} else {
			return null;
		}
	}
	
	 /**
 	 * Gets the element text value.
 	 *
 	 * @param parent the parent
 	 * @param elementTag the element tag
 	 * @return the element text value
 	 */
 	private String getElementTextValue(Element parent, String elementTag) {
		 // If the xml is not coming from a known good source, this method would
		 // have to include safety checks.
		 NodeList list = parent.getElementsByTagName(elementTag);
		 if(list != null) {
			 Element ele = (Element) list.item(0);
			 if(ele != null) {
				 if(ele.getFirstChild() != null) {
					 return ele.getFirstChild().getNodeValue();
				 } else {
					 //System.out.println("getFirstChild() blev null");
					 return "";
				 }
			 } else {
				 //System.out.println("Ele blev null!?");
				 return "";
			 }
		 } else {
			 //System.out.println(elementTag + " list är null");
			 return "";
		 }
	 }

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#deleteContact(org.easypr.organizer.shared.MyContact)
	 */
	@Override
	public Boolean deleteContact(MyContact contact) {
		Key k = KeyFactory.createKey(JDOContact.class.getSimpleName(), contact.getId());
		// Delete the contact
		JDOContact e = pm.getObjectById(JDOContact.class, k);
		pm.deletePersistent(e);
		
		// Delete the meetings
		Query query = pm.newQuery(JDOMeeting.class);
	    query.setFilter("contactid == contactidParam");
	    query.declareParameters("int contactidParam");
	    query.deletePersistentAll(Integer.parseInt(Long.toString(contact.getId())));
	    
	    return true;
	}

	/* (non-Javadoc)
	 * @see org.easypr.organizer.client.BackboneService#deleteMeeting(long)
	 */
	@Override
	public Boolean deleteMeeting(long id) {
		Key k = KeyFactory.createKey(JDOMeeting.class.getSimpleName(), id);
		JDOMeeting e = pm.getObjectById(JDOMeeting.class, k);
		try {
			pm.deletePersistent(e);
		} catch (Exception ex) {
			return false;
		}
		return true;
	}
}
